package com.wmx.controller;

import com.wmx.entity.ResultData;
import com.wmx.entity.TV;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * 1、@RequestParam 绑定查询参数
 * 2、@PathVariable 路径变量
 * 3、@RequestBody 接收数组、List 参数
 * 4、@RequestHeader 轻松获取请求头信息，等同于 request.getHeader(name)
 * 5、@ModelAttribute 模型属性 绑定查询参数、路径变量
 *
 * @author wangMaoXiong
 * @version 1.0
 * @date 2022/4/9 17:09
 */
@RestController
@SuppressWarnings("Duplicates")
public class RequestController {

    private static final Logger logger = LoggerFactory.getLogger(RequestController.class);

    //##########################GetMapping######RequestParam#################################

    /**
     * required 属性默认为 true，此时页面必须传入 uid 参数(值可以为null，但是参数必须有)，否则报 400 错误
     * http://localhost:8080/get1?uid=9889  -> 后台取值 9889
     * http://localhost:8080/get1?uid=  -> 后台取值 null
     * http://localhost:8080/get1  -> 报错：Required Integer parameter 'uid' is not present
     *
     * @param uid ：value 属性的值与参数名称要相同
     * @return
     */
    @GetMapping("get1")
    public String get1(@RequestParam(value = "uid") Integer uid) {
        logger.info("get = >" + uid);
        return "get = >" + uid;
    }

    /**
     * required = false：此时页面可以不传入 uid 参数时，后台 uid 为 null
     * http://localhost:8080/get2?uid=998  -> 后台取值 9889
     * http://localhost:8080/get2?uid=&type=1&type=2&type=3  -> 后台 uid 取值 null，type 接收的值为字符串 1,2,3
     * http://localhost:8080/get2  -> 后台取值 null
     *
     * @param uid : value 属性的值与参数名称要相同
     * @return
     */
    @GetMapping("get2")
    public Map<String, Object> get2(@RequestParam(value = "uid", required = false) Integer uid,
                                    @RequestParam(value = "type", required = false) String type) {
        Map<String, Object> resultMap = new LinkedHashMap<>();
        resultMap.put("uid", uid);
        resultMap.put("type", type);
        logger.info("{}", resultMap);
        return resultMap;
    }

    /**
     * 此时页面没有传入 uid 参数时，将使用默认值 9527
     * http://localhost:8080/get3?uid=888  -> 后台取值 888
     * http://localhost:8080/get3?uid=  -> 后台取值 9527
     * http://localhost:8080/get3  -> 后台取值 9527
     *
     * @param uid : value 属性的值与参数名称要相同
     * @return
     */
    @GetMapping("get3")
    public String get3(@RequestParam(value = "uid", defaultValue = "9527") Integer uid) {
        logger.info("get = >" + uid);
        return "get = >" + uid;
    }

    /**
     * 平时这种方法写的最多，当页面没有传入 uid 参数时，后台 uid 此时默认为 null。
     * http://localhost:8080/get4?uid=666&pid=777  -> 后台取值 666,777
     * http://localhost:8080/get4?uid=666&pid 报错：Failed to convert value of type 'java.lang.String' to required type 'int'; nested exception is java.lang.NumberFormatException: For input string: ""
     * http://localhost:8080/get4?uid=666 报错：Optional int parameter 'uid' is present but cannot be translated into a null value due to being declared as a primitive type
     *
     * @param uid
     * @return
     */
    @GetMapping("get4")
    public String get4(Integer uid, int pid) {
        logger.info("get4 = >" + uid + "," + pid);
        return "get4 = >" + uid + "," + pid;
    }

    /**
     * RequestParam 的 value 属性指定的是调入方传入的参数名称。
     * 1、value 属性不写时，默认会将后面的方法参数名称作为接收的参数名。
     * 2、value 属性指定时，使用指定的值作为接收的参数名称，然后将接收的值赋给后的方法参数
     * http://localhost:8080/get5?pid=77777  -> 后台取值 77777
     * http://localhost:8080/get5?pid=  -> 后台取值 0
     * http://localhost:8080/get5  -> 后台取值 0
     *
     * @param uid
     * @return
     */
    @GetMapping("get5")
    public String get5(@RequestParam(value = "pid", defaultValue = "0") int uid) {
        logger.info("get5 = >" + uid);
        return "get5 = >" + uid;
    }

    /**
     * http://localhost:8080/get6/100?tvName=小米电视V3&tvPrice=8450.14&dateOfProduction=2024/12/25
     * get请求也可以将查询参数，路径变量映射到Java Bean对象中。
     *
     * @param tv
     * @return
     */
    @GetMapping("get6/{tvId}")
    public TV get6(TV tv) {
        // get5 = >TV{tvId=100, tvName='小米电视V3', tvPrice=8450.14, dateOfProduction=Wed Dec 25 00:00:00 CST 2024}
        logger.info("get5 = >" + tv);
        return tv;
    }

    //##########################PathVariable 路径变量#######################################./start======

    /**
     * @PathVariable : 其中的 value 属性值对应 url 中的占位符名称，只有 value 时，可以省略 value 不写，甚至直接括弧不写
     * <p>
     * http://localhost:8080/wmx/movie/1/2/3 ：正确
     * http://localhost:8080/wmx/movie/1/2/3/：正确
     * http://localhost:8080/wmx/movie/1/2/3/4：报错 404
     * http://localhost:8080/wmx/movie/1/2/ ：报错 404，注意 1/2/ 与 1/2/3 是不同的 url
     * http://localhost:8080/wmx/movie/1/2 ：报错 404
     */
    @GetMapping("/wmx/movie/{type}/{region}/{order}")
    public String findMovie1(@PathVariable(value = "type") Integer type,// 三个 type 对应
                             @PathVariable("region") String region,// 省略 value
                             @PathVariable Integer order) {// 直接省略括号，最简形式

        logger.info("type=" + type + ", region=" + region + ", order=" + order);
        return "type=" + type + ", region=" + region + ", order=" + order;
    }

    /**
     * post 请求与 get 请求一样
     * http://localhost:8080/wmx/movie2/1/china/2 ：正确
     * http://localhost:8080/wmx/movie2/1/china/：报错 404，与上面是两个不同的 url
     * http://localhost:8080/wmx/movie2/china/2 ：报错 404
     *
     * @return
     */
    @PostMapping("/wmx/movie2/{type}/china/{order}")
    public String findMovie2(@PathVariable Integer type, @PathVariable Integer order) {
        logger.info("type=" + type + ", order=" + order);
        return "type=" + type + ", order=" + order;
    }

    /**
     * http://localhost:8080/wmx/movie3/1/ 正确
     * http://localhost:8080/wmx/movie3/ 正确
     * "/wmx/movie3/{id}"
     * * 首先明白 :/wmx/movie3/ 与 /wmx/movie3 是相同的 url ，但与 /wmx/movie3/20  是不同的 url
     * required 属性：表示 url 中的参数是否可以为 null，默认为 true
     * * 未设置 required = false 时，则此方法的路径必须满足 "/wmx/movie3/{id}" 的格式，否则就是 404 错误
     * * 有设置 required = false 后，则 "/wmx/movie3/{id}" 中的参数 id 可以为 null，此时就会去匹配没有 id 时的格式，如 "/wmx/movie3"
     * XxxMapping 可以设置多个 url 地址，当然其中的 "/wmx/movie3" 也可以在其它方法中，并不一定要在同一个方法中
     *
     * @param id
     * @return
     */
    @GetMapping(value = {"/wmx/movie3", "/wmx/movie3/{id}"})
    public String findMovie3(@PathVariable(required = false) Integer id) {
        logger.info("id=" + id);
        return "id=" + id;
    }

    /**
     * http://localhost:8080/wmx/movie4/110 正确
     * 1、value 属性不写时，默认会将后面的方法参数名称作为接收的参数名。
     * 2、value 属性指定时，使用指定的值作为接收的参数名称，然后将接收的值赋给后的方法参数
     *
     * @param uid
     * @return
     */
    @GetMapping(value = {"/wmx/movie4/{pid}"})
    public String findMovie4(@PathVariable(value = "pid") Integer uid) {
        logger.info("id=" + uid);
        return "id=" + uid;
    }
    //##########################PathVariable 路径变量#######################################./end========


    //##########################PostMapping######RequestBody#################################./start====

    /**
     * http://127.0.0.1:8080//post/save1?uid=14541&token=845HUY78&type=1&type=2&type=3
     * <p>
     * RequestBody 单个参数时使用 String 类型传输，比如 int、long、flout 等等，都使用 String
     *
     * @param uid
     * @return
     */
    @PostMapping("/post/save1")
    public Map<String, Object> save1(@RequestParam String uid,
                                     @RequestParam String token,
                                     String type) {
        Map<String, Object> resultMap = new LinkedHashMap<>();
        resultMap.put("uid", uid);
        resultMap.put("token", token);
        resultMap.put("type", type);
        return resultMap;
    }

    /**
     * http://localhost:8080/post/save2?token=47384738B
     * 单个参数时使用 String 类型传输，比如 int、long、flout 等等，都使用 String
     *
     * @param ids ：页面传入 json 数据 ["1A","2B","3D"]
     * @return
     */
    @PostMapping("post/save2")
    public Map<String, Object> save2(@RequestBody(required = false) String[] ids,
                                     @RequestParam String token) {
        Map<String, Object> resultMap = new LinkedHashMap<>();
        resultMap.put("ids", ids);
        resultMap.put("token", token);
        return resultMap;
    }

    /**
     * http://localhost:8080/post/save3?token=47384738A
     * required 属性表示参数是否必须要传，默认为 true。此时前端请求体Body参数不能为空，否则就会报错：Required request body is missing。
     * 比如后端是单个POJO或者Map，即使没有参数，前端也必须传值为 {}；
     * 后端是集合或者数组时，即使没有参数，前端必须传值 []；
     * 只有当设置为 false时，前端的body参数才允许为空(可以不传任何值)。
     *
     * @param mapList ：页面传入 json 数据：[{"name":"华安","id":9527,"salary":8890.98}]，直接转换为 List<Map>
     * @return
     */
    @PostMapping("post/save3")
    public Map<String, Object> save3(@RequestBody(required = false) List<Map<String, Object>> mapList,
                                     @RequestParam String token) {
        Map<String, Object> resultMap = new LinkedHashMap<>();
        resultMap.put("mapList", mapList);
        resultMap.put("token", token);
        return resultMap;
    }

    /**
     * http://localhost:8080/post/save4?token=47384738A
     * <p>
     *
     * @param ids ：Long[] ids 参数时，前端也使用整型：[1, 2, 3, 4, 5, 10, 22]
     * @return
     */
    @PostMapping("post/save4")
    public Map<String, Object> save4(@RequestBody Long[] ids,
                                     @RequestParam String token) {
        Map<String, Object> resultMap = new LinkedHashMap<>();
        resultMap.put("ids", ids);
        resultMap.put("token", token);
        return resultMap;
    }

    /**
     * http://localhost:8080/post/save5?token=47384738A
     * <p>
     * 保存单个实体
     *
     * @param tv
     * @param token
     * @return
     */
    @PostMapping("post/save5")
    public Map<String, Object> save5(@RequestBody TV tv,
                                     @RequestParam String token) {
        Map<String, Object> resultMap = new LinkedHashMap<>();
        resultMap.put("tv", tv);
        resultMap.put("token", token);
        return resultMap;
    }

    /**
     * http://localhost:8080/post/save6?token=47384738A
     *
     * @param tvList ：接收页面单个：POJO List。必须加 @RequestBody 注解
     * @param token
     * @return
     */
    @PostMapping("post/save6")
    public Map<String, Object> save6(@RequestBody List<TV> tvList,
                                     @RequestParam String token) {
        Map<String, Object> resultMap = new LinkedHashMap<>();
        resultMap.put("tvList", tvList);
        resultMap.put("token", token);
        return resultMap;
    }

    /**
     * http://localhost:8080/post/save7?tvId=100&tvName=小米电视V3&tvPrice=8450.14&dateOfProduction=2024/12/25&token=uuuY98
     * <p>
     * 保存单个实体 —— 此时没有加 RequestBody 时，实体对象无法绑定请求体数据，只能绑定查询参数，路径变量。
     *
     * @param tv    ：接收页面单个：POJO
     * @param token ：接收页面单个：查询参数
     * @return ：返回结果
     */
    @PostMapping("post/save7")
    public Map<String, Object> save7(TV tv,
                                     @RequestParam String token) {
        Map<String, Object> resultMap = new LinkedHashMap<>();
        resultMap.put("tv", tv);
        resultMap.put("token", token);
        // {
        //     "tv": {
        //         "tvId": 100,
        //         "tvName": "小米电视V3",
        //         "tvPrice": 8450.14,
        //         "dateOfProduction": "2024-12-25 00:00:00"
        //     },
        //     "token": "uuuY98"
        // }
        return resultMap;
    }

    //##########################PostMapping######RequestBody#################################./end======


    //########################## @RequestHeader 轻松获取请求头信息 ######################./start#################

    /**
     * http://localhost:8080/header/headInfo1
     * <p>
     * 基本用法；将请求头中的值注入到参数中。
     *
     * @param userAgent     ：获取用户代理信息，用于记录客户端使用的浏览器或设备类型。默认请求头必须存在。
     * @param authorization ：接收授权信息，可用于身份验证和权限控制。不存在时设置默认值。
     * @param customHeader  ：传递自定义的请求头信息，可用于传递额外的业务参数，名称自定义，客户端请求时，按约定传入。
     * @return
     */
    @GetMapping("header/headInfo1")
    public ResultData getHeadInfo1(@RequestHeader("User-Agent") String userAgent,
                                   @RequestHeader(name = "Authorization", required = false, defaultValue = "Unknown") String authorization,
                                   @RequestHeader(name = "X-Custom-Header", defaultValue = "Unknown") String customHeader) {
        Map<String, String> dataMap = new HashMap<>(8);
        dataMap.put("User-Agent", userAgent);
        dataMap.put("Authorization", authorization);
        dataMap.put("X-Custom-Header", customHeader);
        //  {
        //     "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:125.0) Gecko/20100101 Firefox/125.0",
        //     "Authorization": "wmx",
        //     "X-Custom-Header": "wangMaoXiong"
        //   }
        return ResultData.success(dataMap);
    }

    /**
     * http://localhost:8080/header/headInfo2
     * <p>
     * 除了直接指定请求头的名称外，@RequestHeader注解还支持将请求头映射到自定义的Java对象中，从而实现更灵活的参数处理。
     * 比如将请求头映射到了HttpHeaders对象中，并通过该对象获取了特定请求头的值。这样可以避免使用多个@RequestHeader注解。
     *
     * @param headers
     * @return
     */
    @GetMapping("header/headInfo2")
    public ResultData getHeadInfo2(@RequestHeader HttpHeaders headers) {
        String userAgent = headers.getFirst(HttpHeaders.USER_AGENT);
        String authorization = headers.getFirst(HttpHeaders.AUTHORIZATION);
        String expires = headers.getFirst(HttpHeaders.EXPIRES);
        String contentType = headers.getFirst(HttpHeaders.CONTENT_TYPE);
        String host = headers.getFirst(HttpHeaders.HOST);
        // 其他处理逻辑
        Map<String, String> dataMap = new HashMap<>(8);
        dataMap.put(HttpHeaders.USER_AGENT, userAgent);
        dataMap.put(HttpHeaders.AUTHORIZATION, authorization);
        dataMap.put(HttpHeaders.EXPIRES, expires);
        dataMap.put(HttpHeaders.CONTENT_TYPE, contentType);
        dataMap.put(HttpHeaders.HOST, host);
        // {
        // 	"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:125.0) Gecko/20100101 Firefox/125.0",
        // 	"Authorization": "wmx",
        // 	"Host": "localhost:8080",
        // 	"Expires": null,
        // 	"Content-Type": null
        // }
        return ResultData.success(dataMap);
    }

    /**
     * http://localhost:8080/header/headInfo3
     * <p>
     * 多个请求头的处理：实际开发中，一个HTTP请求可能包含多个请求头，
     * 可以通过在方法参数中使用MultiValueMap<String, String>或Map<String, List<String>>来处理多个请求头。
     *
     * @param headers
     * @return
     */
    @GetMapping("header/headInfo3")
    public ResultData getHeadInfo3(@RequestHeader MultiValueMap<String, String> headers) {
        System.out.println(headers);
        List<String> userAgentList = headers.get("User-Agent");
        List<String> authorizationList = headers.get("Authorization");
        // 其他处理逻辑
        System.out.println("User-Agent: " + userAgentList + ", Authorization: " + authorizationList);
        // {
        // 	"host": ["localhost:8080"],
        // 	"user-agent": ["Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:125.0) Gecko/20100101 Firefox/125.0"],
        // 	"accept": ["*/*"],
        // 	"accept-language": ["zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2"],
        // 	"accept-encoding": ["gzip, deflate, br"],
        // 	"authorization": ["wmx"],
        // 	"x-custom-header": ["wangMaoXiong"],
        // 	"connection": ["keep-alive"],
        // 	"cookie": ["Idea-6d4157db=2a89aef9-7558-4f16-b959-aa0d7c325560"],
        // 	"sec-fetch-dest": ["empty"],
        // 	"sec-fetch-mode": ["cors"],
        // 	"sec-fetch-site": ["same-origin"]
        // }
        return ResultData.success(headers);
    }

    /**
     * http://localhost:8080/header/headInfo4
     *
     * @param headers
     * @return
     */
    @GetMapping("header/headInfo4")
    public ResultData getHeadInfo4(@RequestHeader Map<String, List<String>> headers) {
        List<String> userAgentList = headers.get("User-Agent");
        List<String> authorizationList = headers.get("Authorization");
        // 其他处理逻辑
        System.out.println("User-Agent: " + userAgentList + ", Authorization: " + authorizationList);
        // {
        // 	"host": "localhost:8080",
        // 	"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:125.0) Gecko/20100101 Firefox/125.0",
        // 	"accept": "*/*",
        // 	"accept-language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2",
        // 	"accept-encoding": "gzip, deflate, br",
        // 	"authorization": "wmx",
        // 	"x-custom-header": "wangMaoXiong",
        // 	"connection": "keep-alive",
        // 	"cookie": "Idea-6d4157db=2a89aef9-7558-4f16-b959-aa0d7c325560",
        // 	"sec-fetch-dest": "empty",
        // 	"sec-fetch-mode": "cors",
        // 	"sec-fetch-site": "same-origin"
        // }
        return ResultData.success(headers);
    }
    //########################## @RequestHeader 轻松获取请求头信息 ######################./end==#################


    //########################## @ModelAttribute 模型属性 ##################./start#####################
    // https://www.cnblogs.com/l-liu/p/17210602.html
    // https://blog.csdn.net/weixin_47872288/article/details/139789795
    // https://blog.csdn.net/qq_44761854/article/details/136367848
    // @ModelAttribute和@RequestBody在Spring框架中用于处理请求参数，但它们有一些关键的区别‌。
    //
    // 数据来源和绑定方式
    // @ModelAttribute‌：用于绑定请求参数到方法的参数或方法的返回值上。数据可以来自于请求的查询参数、表单字段或路径变量。它将请求参数与方法参数进行绑定，并将其转化为指定类型的对象。可以通过@RequestParam注解指定参数名称，也可以根据属性名自动进行匹配‌
    // 。
    // @RequestBody‌：用于将请求体中的数据绑定到方法的参数上。数据通常以JSON、XML或其他格式发送，并将其转化为Java对象。Spring会自动根据请求体的内容类型进行数据解析，并将其转换为指定的Java对象‌
    // 。
    // 适用场景
    // @ModelAttribute‌：适用于处理表单提交、URL查询参数等传统的表单数据。可以绑定多个参数到一个Java对象上，方便处理复杂的请求参数‌
    // 。
    // @RequestBody‌：适用于处理JSON、XML等格式的请求体数据，通常用于RESTful风格的API接口，方便将请求数据转化为对象进行处理‌
    // 。
    // 具体使用场景和效果
    // @ModelAttribute‌：在使用时，SpringMVC会默认将参数绑定到模型中，即在视图中使用时会自动添加到model中。例如，在处理表单提交时，可以将多个表单字段绑定到一个对象上，简化数据处理‌
    // 。
    // @RequestBody‌：将请求体中的数据直接绑定到方法的入参上，不会将数据添加到model中。例如，在处理JSON格式的API请求时，可以直接将请求体中的数据转化为Java对象进行处理‌
    // 。

    //########################## @ModelAttribute 模型属性 ##################./end==#####################


}
